home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
IRIX 6.2 Applications 1996 May
/
SGI IRIX 6.2 Applications 1996 May.iso
/
dist
/
impr_dev.idb
/
usr
/
impressario
/
src
/
libprintui
/
PrintOptionPanel.c.z
/
PrintOptionPanel.c
Wrap
C/C++ Source or Header
|
1996-05-06
|
49KB
|
1,681 lines
/**************************************************************************
*
* Copyright (c) 1993 Silicon Graphics, Inc.
* All Rights Reserved
*
* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF SGI
*
* The copyright notice above does not evidence any actual of intended
* publication of such source code, and is an unpublished work by Silicon
* Graphics, Inc. This material contains CONFIDENTIAL INFORMATION that is
* the property of Silicon Graphics, Inc. Any use, duplication or
* disclosure not specifically authorized by Silicon Graphics is strictly
* prohibited.
*
* RESTRICTED RIGHTS LEGEND:
*
* Use, duplication or disclosure by the Government is subject to
* restrictions as set forth in subdivision (c)(1)(ii) of the Rights in
* Technical Data and Computer Software clause at DFARS 52.227-7013,
* and/or in similar or successor clauses in the FAR, DOD or NASA FAR
* Supplement. Unpublished - rights reserved under the Copyright Laws of
* the United States. Contractor is SILICON GRAPHICS, INC., 2011 N.
* Shoreline Blvd., Mountain View, CA 94039-7311
**************************************************************************
*
* File: PrintOptionPanel.c
*
* Description: Contains functions for handling the printer specific
* option panel programs (a.k.a. gui_model files). These functions
* are private to libprintui and should not be used for normal
* application development. Normal access to the printer specific
* options panels is obtained via the PrintBox widget.
*
* These functions comprise the PrintOptionPanel portion of libprintui.
* To access these function include PrintOptionPanel.h which is
* installed in /usr/include/Sgm. All functions in this portion of
* libprintui are prefixed with PuiOptionPanel.
*
* The functions handle creation, display, tear down and window
* management of a printer option panel program. The normal life
* cycle is:
*
* 1. Instantiate a printer option panel program using
* PuiOptionPanelCreate. The function will test if there is
* an option panel for the specified printer but you can do
* this explicitly with a call to PuiOptionPanelAlive.
*
* 2. Run the options program with a call to PuiOptionPanelExec.
* This will execute the program and display the window.
*
* 3. You can explicitly kill the optin panel with a call to
* PuiOptionPanelKill.
*
* 4. When you no longer need the option panel, call
* PuiOptionPanelDestroy. This will implicitly do a
* PuiOptionPanelKill if the option panel is running.
*
* The option panel communicates with your app via callbacks. Using
* the function PuiOptionPanelSetCallback for each of the following
* callbacks you may register a single function and spcify client
* data. Look at the definition of PuiOptionPanelCallbackStruct
* in the header file for the structure fields that are valid for
* each callback. The callbacks are:
*
* PuiNoptionPanelErrorCallback
* Called when an error has occurred. If an error has occurred
* during a PuiOptionPanelExec that causes the option
* panel to not run you will get the error callback followed
* but a death callback to indicate the option panel could not
* be run. Note that under certain error conditions you
* will get a death callback with no corresponding error
* callback.
*
* PuiNoptionPanelMapCallback
* Called when the option panel maps on the screen.
*
* PuiNoptionPanelDeathCallback
* Called when the option panel program terminates normally or
* abnormally.
*
* PuiNoptionPanelDataCallback
* Called when an option string is avaiable. Do not free
* the string passed in the callback. Copy the string if
* you wish to preserve it.
*
**************************************************************************/
#ident "$Revision: 1.10 $"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <signal.h>
#include <limits.h>
#include <syslog.h>
#include <pwd.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <X11/Xproto.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <Sgm/PrintOptionPanel.h>
#include "PuiI.h"
#include "RootWin.h"
/* Printer option panel info */
#define OPTION_PANEL_DIR "/var/spool/lp/gui_interface"
#define SPOOL_APP_DEFAULTS_DIR "/var/spool/lp/app-defaults"
/* Callback macros */
#define InitCallback(cb) \
cb.reason = PuiCR_OPTION_NONE; \
cb.errorCode = 0; \
cb.wid = None; \
cb.options = NULL;
/* File scope variables */
static Widget rootWin; /* Root window widget for events */
static char *username; /* User name based on uid */
/* Local functions */
static char* FindOptionPanel(const char *printer);
static char** BuildArgList(PuiOptionPanel *panel,
const PuiOptionPanelArgs *args);
static Window FindTopWindow(Display *disp, Window win);
static Window SearchChildWindows(Display *disp, Window win);
static Boolean WindowIsOptionPanel(PuiOptionPanel *panel, Window win);
static int GetWMState(Display *disp, Window win);
static void HandleError(PuiOptionPanel *panel, int errCode);
static void HandleMappingCB(Widget root, XtPointer clientData, XEvent *event,
Boolean *contDispatch);
static void HandleParentCB(Widget root, XtPointer clientData, XEvent *event,
Boolean *contDispatch);
static void HandleInputCB(XtPointer clientData, int *fdp, XtInputId *inputID);
static void HandleDeath(PuiOptionPanel *panel);
static void HandleData(PuiOptionPanel *panel, char *optionStr);
static void CallDeathCallback(PuiOptionPanel *panel);
static void RaisePanel(PuiOptionPanel *panel);
static Widget FindShell(Widget w);
static void SetXSearchPath(char *printerName);
/**************************************************************************
*
* Function: _PuiOptionPanelExists
*
* Description: Tests whether there is a printer specific option panel
* program for the specified printer.
*
* Parameters:
* printer (I) - name of the printer for which an option panel is
* desired.
*
* Return: True if option panel exists and False if not.
*
**************************************************************************/
Boolean _PuiOptionPanelExists(const char *printer)
{
register char *ptr;
assert(printer != NULL);
if ((ptr = FindOptionPanel(printer)) == NULL)
return False;
XtFree(ptr);
return True;
}
/**************************************************************************
*
* Function: _PuiOptionPanelAlive
*
* Description: Tests whether the specified option panel program is
* executing.
*
* Parameters:
* panel (I) - option panel structure
*
* Return: True if option panel program is executing and False if not.
*
**************************************************************************/
Boolean _PuiOptionPanelAlive(PuiOptionPanel *panel)
{
assert(panel != NULL);
return ((panel->pid == 0 || (kill(panel->pid, 0) < 0))? False: True);
}
/**************************************************************************
*
* Function: _PuiOptionPanelCreate
*
* Description: Creates an instance of a printer specific options panel.
* The option panel data structure is allocated and initialized.
* The option panel program is not actually run until a
* PuiOptionPanelExec is performed.
*
* Parameters:
* parent (I) - widget ID of the "parent" of the option panel. The
* parent must be a descendent of the Core widget
* class.
* printerName (I) - name of the printer whose option panel is to
* be launched.
* followParent (I) - True = follow parent's mapping changes. If the
* parent unmap, the option panel window, if up,
* will be withdrawn. When the parent maps, the
* option panel will be mapped again. Note that
* this only works if we can detect the option panel
* window mapping. If we cannot detect the mapping
* we can;t get the panel WID so we cannot effect
* its window.
*
* Return: If the printer option panel program exists, a pointer to an
* option panel structure is returned. If the panel program could
* not be found NULL will be returned. Note that the storage for the
* option panel structure is allocated by the function. It is the
* caller's responsibility to free this storage when no longer needed
* using a call to the _PuiOptionPanelDestroy function.
*
**************************************************************************/
PuiOptionPanel* _PuiOptionPanelCreate(Widget parent, const char *printerName,
Boolean followParent)
{
char *optionProg;
PuiOptionPanel *optionPanel;
struct passwd *user_pw;
Widget shell;
/*
* Sanity check our inputs
*/
assert(parent != NULL);
assert(printerName != NULL);
/*
* Determine if a printer option panel program exists for the
* specified printer.
*/
if ((optionProg = FindOptionPanel(printerName)) == NULL)
return NULL;
/*
* Find the Shell on which the parent widget lives. This shell will be
* used as the parent for the RootWin widget as well as for catching
* map/unmap events if we are to follow the parent.
*/
shell = FindShell(parent);
/*
* If this is the first time we are here, determine
* the user's login name
*/
if (!username) {
if ((user_pw = getpwuid(getuid())) == NULL) {
char buf[30];
sprintf(buf, "uid%u", getuid());
username = XtNewString(buf);
} else
username = XtNewString(user_pw->pw_name);
}
/*
* Allocate storage for the option panel structure
*/
optionPanel = (PuiOptionPanel*)XtMalloc(sizeof(PuiOptionPanel));
/*
* Initialize the option panel structure
*/
optionPanel->name = XtNewString(printerName);
optionPanel->pathname = optionProg;
optionPanel->parent = parent;
optionPanel->pid = 0;
optionPanel->dataFd = -1;
optionPanel->ptyFd = -1;
optionPanel->inputId = 0;
optionPanel->wid = None;
optionPanel->wantWID = False;
optionPanel->followParent = followParent;
optionPanel->shell = shell;
optionPanel->errorCallback = NULL;
optionPanel->mapCallback = NULL;
optionPanel->deathCallback = NULL;
optionPanel->dataCallback = NULL;
optionPanel->errorClientData = NULL;
optionPanel->mapClientData = NULL;
optionPanel->deathClientData = NULL;
optionPanel->dataClientData = NULL;
return optionPanel;
}
/**************************************************************************
*
* Function: _PuiOptionPanelDestroy
*
* Description: Terminates the option panel program if it is running,
* and deallocates the option panel structure storage thereby
* destroying the option panel handle. The handle should not be
* referenced after a call to this function.
*
* Parameters:
* panel (I) - pointer to an option panel structure
*
* Return: none
*
**************************************************************************/
void _PuiOptionPanelDestroy(PuiOptionPanel *panel)
{
/*
* Sanity checks
*/
if (!panel)
return;
/*
* Kill the option panel if it is alive
*/
_PuiOptionPanelKill(panel, False);
/*
* Deallocate the storage for the structure
*/
if (panel->name) XtFree(panel->name);
if (panel->pathname) XtFree(panel->pathname);
XtFree((char*)panel);
}
/**************************************************************************
*
* Function: _PuiOptionPanelSetCallback
*
* Description: Registers the specified function as the callback function
* for the specified condition. Currently, only a single callback
* function can be registered per condition.
*
* Parameters:
* panel (I) - option panel structure
* callbackName (I) - name of callback (e.g. optionPanelErrorCallback)
* callbackProc (I) - function to call. Setting this to NULL
* indicates no interest in the callback.
* clientData (I) - client specific information
*
* Return: none
*
**************************************************************************/
void _PuiOptionPanelSetCallback(PuiOptionPanel *panel,
PuiOptionPanelCallbackName callbackName,
PuiOptionPanelCallbackProc callbackProc,
XtPointer clientData)
{
/*
* Sanity checks
*/
assert(panel != NULL);
/*
* Assign the callback
*/
switch (callbackName) {
case PuiNoptionPanelErrorCallback:
panel->errorCallback = callbackProc;
panel->errorClientData = clientData;
break;
case PuiNoptionPanelMapCallback:
panel->mapCallback = callbackProc;
panel->mapClientData = clientData;
break;
case PuiNoptionPanelDeathCallback:
panel->deathCallback = callbackProc;
panel->deathClientData = clientData;
break;
case PuiNoptionPanelDataCallback:
panel->dataCallback = callbackProc;
panel->dataClientData = clientData;
break;
}
}
/**************************************************************************
*
* Function: _PuiOptionPanelExec
*
* Description: Launches a printer options panel program. The program will
* not be launched if it is already running. To catch any errors
* that may occurr during the launching process an error callback
* should be registered.
*
* Parameters:
* panel (I) - optionspanel structure
* args (I) - option panel command line arguments structure. If
* NULL is specified a default set of command line
* arguments will be used. The default command line
* arguments structure is:
*
* userName = username from getpwuid of getuid
* filenames = NULL
* options = NULL
* clientXtOptions = NULL
* numClientXtOptions = 0
*
* This results in a command line for the option panel
* that looks like:
*
* <printername> <username> ""
*
* If args is specified but the username field is
* NULL, the default username will be used.
*
* Return: None. To catach errors, register an error callback.
*
**************************************************************************/
void _PuiOptionPanelExec(PuiOptionPanel *panel, const PuiOptionPanelArgs *args)
{
char **argv, *ptyName;
int status, dataPipe[2];
pid_t retv, pid1, pid2;
struct sigaction sact, origSact;
Boolean errorCond;
/*
* Sanity checks
*/
assert(panel != NULL);
/*
* You will notice that throughout the program we create a root window
* widget before we use it. This is in case the root window widget was
* destroyed because its parent was destroyed. If the root window widget
* if stiff valid, the _PuiCreateRootWin function simply returns the
* widget ID of the already existing root window widget. This is not a
* leak just a requirement for working with the root window widget.
*/
rootWin = _PuiCreateRootWin(panel->parent, "_puiRootWin", NULL, 0);
/*
* Test whether the option panel is already running. If it is
* running, deiconify it and raise it to the top of the window stack.
* We also generate a call on the mapping callback as if the window
* had just mapped. That way, if someone wants to clear a busy
* cursor on the map event they can.
*/
if (_PuiOptionPanelAlive(panel)) {
RaisePanel(panel);
if (panel->mapCallback) {
PuiOptionPanelCallbackStruct cb;
InitCallback(cb);
cb.reason = PuiCR_OPTION_MAP;
cb.wid = panel->wid;
(panel->mapCallback)(panel, panel->mapClientData, &cb);
}
return;
}
/*
* Build the command line argument list
*/
argv = BuildArgList(panel, args);
/*
* Put an event handler on the root window to catch
* the option panel's map event.
*/
XtAddEventHandler(rootWin, SubstructureNotifyMask, False,
HandleMappingCB, (XtPointer)panel);
XFlush(XtDisplay(rootWin));
/*
* Open a pipe to read from the option panel's stdout
*/
if (pipe(dataPipe) < 0) {
HandleError(panel, errno);
XtRemoveEventHandler(rootWin, SubstructureNotifyMask, False,
HandleMappingCB, (XtPointer)panel);
XtFree((char*)argv);
CallDeathCallback(panel);
return;
}
/*
* Set SIGCLD to SIG_DFL.
*
* We are going to fork a child and that child will fork a child
* (sounds biblical). We default SIGCLD for the first child so that
* app signal handlers do not get called when the first child terminates.
* Using POSIX handling, any app child that dies will have its SIGCLD
* reasserted when we un-default the signal mask. Meanwhile the
* second child will be forked off in SIGCLD ignore mode so that it
* never generates a SIGCLD and never leaves a zombie.
*/
sact.sa_handler = SIG_DFL;
sigemptyset(&sact.sa_mask);
sact.sa_flags = 0;
sigaction(SIGCLD, &sact, &origSact);
/*
* Now for some psuedo-terminal hocus pocus. The problem is that we
* want the second child (i.e. the options panel program) to terminate
* (i.e. be sent a SIGHUP) when the parent (its grandparent) dies for
* any reason. We cannot use prctl(PR_TERMCHILD) because of the death
* of the intervening first child. So we use a pty instead. The parent
* holds the master end and the second child holds the slave end. When
* the master end closes due to death of the parent, the second child
* is sent a SIGHUP. This works for kill -9 and all other cases.
*/
ptyName = _getpty(&panel->ptyFd, O_RDWR, 0600, 0);
if (ptyName == NULL) {
HandleError(panel, errno);
sigaction(SIGCLD, &origSact, NULL);
close(dataPipe[0]);
close(dataPipe[1]);
panel->ptyFd = -1;
XtRemoveEventHandler(rootWin, SubstructureNotifyMask, False,
HandleMappingCB, (XtPointer)panel);
XtFree((char*)argv);
CallDeathCallback(panel);
return;
}
/*
* Indicate to the event handler that we want the WID of the option
* panel program.
*/
panel->wantWID = True;
/*
* Fork the first child
*/
if ((pid1 = fork()) < 0) {
HandleError(panel, errno);
sigaction(SIGCLD, &origSact, NULL);
close(dataPipe[0]);
close(dataPipe[1]);
panel->wantWID = False;
XtRemoveEventHandler(rootWin, SubstructureNotifyMask, False,
HandleMappingCB, (XtPointer)panel);
XtFree((char*)argv);
CallDeathCallback(panel);
return;
}
/* First Child ----------------------------------------------------*/
if (pid1 == 0) {
register int i;
pid_t pid;
int fd;
/*
* Set the SIGCLD for the second child to SIG_IGN so that
* we do not get SIGCLD and we do not get zombies.
*/
sact.sa_handler = SIG_IGN;
sigemptyset(&sact.sa_mask);
sact.sa_flags = 0;
sigaction(SIGCLD, &sact, NULL);
/*
* Fork the second child. The first child writes the pid
* of the second child to the pipe. The parent will read
* the pid from the pipe.
*/
if ((pid = fork()) < 0) {
write(dataPipe[1], &pid, sizeof(pid_t));
exit(errno);
}
if (pid > 0) {
write(dataPipe[1], &pid, sizeof(pid_t));
exit(0);
}
/* Second Child ------------------------------------------------*/
/*
* The second child must become a process group leader and loose
* its controlling terminal so that we can use the pty as the
* controlling terminal.
*/
setsid();
/*
* Ensure that SIGHUP is DFL
*/
sact.sa_handler = SIG_DFL;
sigemptyset(&sact.sa_mask);
sact.sa_flags = 0;
sigaction(SIGHUP, &sact, NULL);
/*
* Connect our stdout to the write end of the pipe
*/
if (dataPipe[1] != 1) {
close(1);
fcntl(dataPipe[1], F_DUPFD, 1);
}
/*
* Close all file descriptors except 0, 1 and 2.
*/
for (i = getdtablesize() - 1; i > 2; i--)
close(i);
/*
* We open the slave end of the psuedo-terminal so that
* it becomes our controlling terminal and we get a HUP
* when the parent dies.
*/
if (open(ptyName, O_RDWR) < 0) {
syslog(LOG_ERR, "Could not open pty for option panel %s: %m",
panel->pathname);
exit(errno);
}
/*
* Make sure we look for app-defaults in the spooling
* subdirectory first before looking in the normal place.
* This is because network printers will have the app-defaults
* file for their option panel copied to a spooler app-default
* directory by lputil.
*/
SetXSearchPath(argv[0]);
/*
* Believe it or not, it is now time to run the
* option panel program. If we get past exec, an
* error has occurred and we exit with errno.
*/
execv(panel->pathname, argv);
syslog(LOG_ERR, "Could not exec option panel %s: %m", panel->pathname);
exit(errno);
}
/* Parent ---------------------------------------------------------*/
/*
* Close the write end of parent's pipe
*/
close(dataPipe[1]);
/*
* Read the second child's pid from the pipe
*/
read(dataPipe[0], &pid2, sizeof(pid_t));
/*
* Wait for the first child to terminate. Its exit status is
* the either 0 if the second child was successfully forked, or
* it is the value of errno if an error occurred while forking the
* second child.
*/
while (((retv = waitpid(pid1, &status, 0)) < 0) && oserror() == EINTR)
;
/*
* If we had an error executing the second child handle it
*/
errorCond = False;
if (retv < 0) {
errorCond = True;
HandleError(panel, errno);
} else if (pid2 < 0 || (WIFEXITED(status) && WEXITSTATUS(status) != 0)) {
errorCond = True;
HandleError(panel, WEXITSTATUS(status));
}
if (errorCond) {
sigaction(SIGCLD, &origSact, NULL);
close(dataPipe[0]);
close(panel->ptyFd);
panel->ptyFd = -1;
panel->wantWID = False;
XtRemoveEventHandler(rootWin, SubstructureNotifyMask, False,
HandleMappingCB, (XtPointer)panel);
XtFree((char*)argv);
CallDeathCallback(panel);
return;
}
/*
* Restore the original signal handler
*/
sigaction(SIGCLD, &origSact, NULL);
/*
* Free the command line arguments list
*/
XtFree((char*)argv);
/*
* Save the option panel's pid and fd
*/
panel->pid = pid2;
panel->dataFd = dataPipe[0];
/*
* Listen for data on the pipe
*/
panel->inputId = XtAppAddInput(XtWidgetToApplicationContext(panel->shell),
panel->dataFd, (XtPointer)XtInputReadMask,
HandleInputCB, (XtPointer)panel);
}
/**************************************************************************
*
* Function: _PuiOptionPanelKill
*
* Description: Kills an option panel. If the panel is not running, this
* function silently returns. If the panel is running we send it
* a SIGTERM.
*
* Parameters:
* panel (I) - option panel structure
* notify (I) - True = call death callback if panel is running
*
* Return: none
*
**************************************************************************/
void _PuiOptionPanelKill(PuiOptionPanel *panel, Boolean notify)
{
pid_t pid;
/*
* Sanity checks
*/
assert(panel != NULL);
/*
* First test whether the panel is alive. If it is not we simply
* return.
*/
if (!_PuiOptionPanelAlive(panel))
return;
/*
* Remember the pid so that we can clean up and then do the kill
*/
pid = panel->pid;
HandleDeath(panel);
/*
* We will issue the kill to remove the panel
*/
kill(pid, SIGTERM);
/*
* Close the pty
*/
close(panel->ptyFd);
panel->ptyFd = -1;
/*
* Call the death callback if desired
*/
if (notify)
CallDeathCallback(panel);
}
/*
============================================================================
LOCAL FUNCTIONS
============================================================================
*/
/**************************************************************************
*
* Function: FindOptionPanel
*
* Description: Looks for a printer option panel.
*
* Parameters:
* printer (I) - name of the printer whose option panel is desired.
*
* Return: If the option panel exists and is executable a pointer to the
* full pathname of the option panel program is returned. It is the
* caller's responsibility to free the storage for the returned
* pathname. If the option panel is not found, NULL is returned.
*
**************************************************************************/
static char* FindOptionPanel(const char *printer)
{
char *optionPathname = (char*)XtMalloc(strlen(printer) +
strlen(OPTION_PANEL_DIR) + 20);
/*
* First look in the ELF subdirectory for the option panel
* and then in the primary option panel directory.
*/
sprintf(optionPathname, "%s/ELF/%s.gui", OPTION_PANEL_DIR, printer);
if (access(optionPathname, R_OK | X_OK) < 0) {
/*
Comment this out becuase this dir contains COFF and 6.2+ will not
work with COFF.
sprintf(optionPathname, "%s/%s.gui", OPTION_PANEL_DIR, printer);
if (access(optionPathname, R_OK | X_OK) < 0) {
*/
XtFree(optionPathname);
optionPathname = NULL;
/*
}
*/
}
return optionPathname;
}
/**************************************************************************
*
* Function: BuildArgList
*
* Description: Constructs an array of command line arguments for the
* option's panel program exec. The array is NULL terminated.
*
* Parameters:
* panel (I) - option panel structure
* args (I) - option panel arguments structure
*
* Return: Pointer to NULL terminated array of command line arguments.
* It is the caller's job to free the array. Note that only the
* array itself needs to be freed, the strings themselves are not
* allocated here.
*
**************************************************************************/
static char** BuildArgList(PuiOptionPanel *panel,
const PuiOptionPanelArgs *args)
{
char **argv;
register int n = 0, i;
/*
* Allocate enough room in the list for the fixed options plus
* any Xt client options that are specified.
*/
if (args)
argv = (char**)XtCalloc(10 + args->numClientXtOptions, sizeof(char*));
else
argv = (char**)XtCalloc(10, sizeof(char*));
/*
* Build the array of arguments
*/
argv[n++] = panel->name;
argv[n++] = (args && args->userName)? args->userName: username;
argv[n++] = (args && args->filenames)? args->filenames: "";
if (args) {
argv[n] = (args->options)? args->options: ""; n++;
for (i = 0; i < args->numClientXtOptions; i++)
argv[n++] = (args->clientXtOptions[i])?
args->clientXtOptions[i]: "";
}
return argv;
}
/**************************************************************************
*
* Function: HandleError
*
* Description: Handles a execution error by calling the user's error
* callback if one is registered.
*
* Parameters:
* panel (I) - option panel structure
* errCode (I) - error code to report in callback
*
* Return: none
*
**************************************************************************/
static void HandleError(PuiOptionPanel *panel, int errCode)
{
PuiOptionPanelCallbackStruct cb;
/*
* If there is no callback function don't do anything
*/
if (!panel->errorCallback)
return;
/*
* Initialize the structure
*/
InitCallback(cb);
/*
* Set appropriate fields and call the callback function
*/
cb.reason = PuiCR_OPTION_ERROR;
cb.errorCode = errCode;
(panel->errorCallback)(panel, panel->errorClientData, &cb);
}
/**************************************************************************
*
* Function: HandleParentCB
*
* Description: Event handler for structure notify events from the parent
* window. The purpose of this function is to allow the option panel
* to follow the mapping state of its parent. If the parent unmaps,
* the option panel window is withdrawn. If the parent maps, the
* option panel window is mapped. This event handler is only installed
* when the followParent parameter is True and an option panel is
* running.
*
* Parameters:
* w (I) - widget on which the event occured
* clientData (I) - a pointer to the option panel structure
* event (I) - the X event that triggered the callback
* contDispatch (I) - always set to True so that the event propagates.
*
* Return: none
*
**************************************************************************/
static void HandleParentCB(Widget w, XtPointer clientData, XEvent *event,
Boolean *contDispatch)
{
PuiOptionPanel *panel = (PuiOptionPanel*)clientData;
XWMHints *hints;
int x, y, ourState;
Window root;
unsigned int width, height, bwidth, depth;
/*
* Make sure we have a WID and that the event is of a type
* we are interested in. Also make sure that the window effected
* is the parent window.
*/
if (panel->wid == None ||
(event->type != MapNotify && event->type != UnmapNotify) ||
(XtWindow(w) != XtWindow(panel->shell)))
return;
/*
* First get our window state. Assume normal state if can't get it.
*/
if ((ourState = GetWMState(XtDisplay(w), panel->wid)) < 0)
ourState = NormalState;
/*
* Now if the parent has unmapped and we are not already withdrawn,
* save the current state of the option panel window and withdraw it.
*/
if (event->type == UnmapNotify && ourState != WithdrawnState) {
if ((hints = XGetWMHints(XtDisplay(w), panel->wid)) == NULL)
panel->stateHints.flags = StateHint;
else {
panel->stateHints = *hints;
XFree((char*)hints);
}
panel->stateHints.initial_state = ourState;
if (XGetGeometry(XtDisplay(w), panel->wid, &root, &x, &y,
&width, &height, &bwidth, &depth) == 0)
panel->sizeHints.flags = 0;
else {
panel->sizeHints.flags = USPosition | USSize;
panel->sizeHints.x = x;
panel->sizeHints.y = y;
panel->sizeHints.width = width;
panel->sizeHints.height = height;
}
XWithdrawWindow(XtDisplay(w), panel->wid, DefaultScreen(XtDisplay(w)));
}
/*
* Similarly if the parent has mapped and we are withdrawn, bring
* our option panel window back to its original state and position
* on the screen.
*/
else if (event->type == MapNotify && ourState == WithdrawnState) {
XSetWMHints(XtDisplay(w), panel->wid, &panel->stateHints);
XSetWMNormalHints(XtDisplay(w), panel->wid, &panel->sizeHints);
XMapWindow(XtDisplay(w), panel->wid);
}
}
/**************************************************************************
*
* Function: HandleMappingCB
*
* Description: Event handler for substructure notify events from
* the root window. The sole purpose of this function is to
* detect the initial mapping of the optionpanel window on the
* root window. As soon as this event is detected, this event handler
* removes itself.
*
* Parameters:
* root (I) - widget on which the event occured
* clientData (I) - a pointer to the option panel structure
* event (I) - the X event that triggered the callback
* contDispatch (I) - always set to True so that the event propagates.
*
* Return: none
*
**************************************************************************/
static void HandleMappingCB(Widget root, XtPointer clientData, XEvent *event,
Boolean *contDispatch)
{
PuiOptionPanel *panel = (PuiOptionPanel*)clientData;
PuiOptionPanelCallbackStruct cb;
Window topWindow;
/*
* Make sure we have a valid root window widget
*/
rootWin = _PuiCreateRootWin(panel->parent, "_puiRootWin", NULL, 0);
/*
* Do nothing if we aren't looking for an option panel WID
* or if the event is not a window mapping event.
*/
if (!panel->wantWID || event->type != MapNotify)
return;
/*
* If the window being mapped is override redirect we are
* not interested. The WM puts up an override redirect window
* for apps that don't have explicit placement.
*/
if (event->xmap.override_redirect)
return;
/*
* We want to get the WID of the top level window. A reparenting
* window manager will return the WID of its reparented window in
* the event structure. What we want is the top level window below
* that. In any case the ICCCM define the top level window as that
* which is not override redirect and has the WM_STATE property set
* on it. If the property is not set, the top level is considered
* the window we pass in.
*/
topWindow = FindTopWindow(XtDisplay(root), event->xmap.window);
/*
* Now that we have the top level window we want to see if it is
* the option panel program . We do this by first looking at the
* instance name of the program and comparing it against the current
* printer name. If this fails we try the wWM name and finally the
* icon name.
*/
if (WindowIsOptionPanel(panel, topWindow) == False)
return;
/*
* If we go here, we have an option panel program. First thing
* is to show no more interest in getting WIDs. Then we remove
* the event handler and set the panel structures WID variable.
*/
panel->wantWID = False;
XtRemoveEventHandler(rootWin, SubstructureNotifyMask, False,
HandleMappingCB, (XtPointer)panel);
panel->wid = topWindow;
/*
* If we have been asked to follow the parent mapping. In order to
* do this we must install an event handler on the shell that the
* parent widget lives on. Only the shell will get the map/unmap
* event.
*/
if (panel->followParent) {
XtAddEventHandler(panel->shell, StructureNotifyMask, False,
HandleParentCB, (XtPointer)panel);
XFlush(XtDisplay(rootWin));
}
/*
* Now call the option panel mapping event callback, if any
*/
if (!panel->mapCallback)
return;
InitCallback(cb);
cb.reason = PuiCR_OPTION_MAP;
cb.wid = panel->wid;
(panel->mapCallback)(panel, panel->mapClientData, &cb);
}
/**************************************************************************
*
* Function: GetWMState
*
* Description: If the WM_STATE atom is defined on the specified top level
* window, return its value.
*
* Parameters:
* disp (I) - X display
* win (I) - top level window to read WM_STATE from
*
* Return:
* -2 = WM_STATE atom not interned.
* -1 = WM_STATE atom not found on top level window.
* 0 = WithdrawnState
* 1 = NormalState
* 2 = [obsolete]
* 3 = IconicState
* 4 = [obsolete]
*
**************************************************************************/
static int GetWMState(Display *disp, Window win)
{
static Atom WM_STATE = None;
Atom actualType = None;
int actualFormat;
unsigned long nitems, bytesAfter;
unsigned char *propReturn;
/*
* First attempt to get the WM_STATE atom.
*/
if (WM_STATE == None)
WM_STATE = XInternAtom(disp, "WM_STATE", True);
if (WM_STATE == None)
return -2;
/*
* Next we attempt to get the WM_STATE property off the window.
* We consider the property set on the window if the function
* returns Success and actualType != None.
*/
if (XGetWindowProperty(disp, win, WM_STATE, 0, 1, False,
AnyPropertyType, &actualType, &actualFormat,
&nitems, &bytesAfter, &propReturn) == Success &&
actualType != None) {
int state = (int)*((CARD32*)propReturn);
XFree((char*)propReturn);
return state;
}
/*
* Never found WM_STATE.
*/
return -1;
}
/**************************************************************************
*
* Function: FindTopWindow
*
* Description: Finds the top level window in the window heirarchy rooted
* at the specified window. The ICCCM defines the top level window
* as that window that is not override redirect and has the WM_STATE
* propery. If the WM_STATE property is not defined or found then
* the window passed in is considred the top level window.
*
* Parameters:
* disp (I) - X display
* win (I) - window from which to start the search for the top
* level window.
*
* Return: Top level window.
*
**************************************************************************/
static Window FindTopWindow(Display *disp, Window win)
{
int state;
Window retWin;
/*
* Next we attempt to get the WM_STATE property off the window.
* If we cannot even intern the WM_STATE atom forget even searching.
* IF the WM_STATE property is set we found the top.
*/
if ((state = GetWMState(disp, win)) == -2)
return win;
if (state != -1)
return win;
/*
* If we got here, the WM_STATE property is not set on the window
* we passed in so we do a breadth first search of the window
* hierarchy.
*/
if ((retWin = SearchChildWindows(disp, win)) != None)
return retWin;
/*
* Never found WM_STATE so per ICCCM we assume the original window
* if the top level window.
*/
return win;
}
/**************************************************************************
*
* Function: SearchChildWindows
*
* Description: Performs a breadth first recursive search on the window
* hierarchy looking for the specified property.
*
* Parameters:
* disp (I) - X display
* win (I) - window to root search
*
* Return: Window ID of window containing specified atom or None if atom
* not found on any window.
*
**************************************************************************/
static Window SearchChildWindows(Display *disp, Window win)
{
Window root, parent, *children;
Window retWin = None;
unsigned int numChildren;
register int i;
/*
* Get the window heirarchy for the current window
*/
if (!XQueryTree(disp, win, &root, &parent, &children, &numChildren))
return None;
/*
* Search the children for the Atom property
*/
for (i = 0; retWin == None && i < numChildren; i++) {
if (GetWMState(disp, children[i]) != -1)
retWin = children[i];
}
/*
* Did not find it on any children at this level so we
* must recurse down a level.
*/
for (i = 0; retWin == None && i < numChildren; i++)
retWin = SearchChildWindows(disp, children[i]);
/*
* Free the sotrage for the child window array
*/
if (children)
XFree((char*)children);
return retWin;
}
/**************************************************************************
*
* Function: WindowIsOptionPanel
*
* Description: Determines if the specified top level window is an
* option panel program. We do this by first looking at the
* instance name of the program and comparing it against the current
* printer name. If this fails we try the wWM name and finally the
* icon name.
*
* Parameters:
* panel (I) - option panel structure
* win (I) - top level window to test
*
* Return: True if window is an option panel program, False if not.
*
**************************************************************************/
static Boolean WindowIsOptionPanel(PuiOptionPanel *panel, Window win)
{
XClassHint classHints;
XTextProperty textProp;
/*
* Make sure we have a valid root window widget
*/
rootWin = _PuiCreateRootWin(panel->parent, "_puiRootWin", NULL, 0);
/*
* First try to get the class hints
*/
if (XGetClassHint(XtDisplay(rootWin), win, &classHints)) {
int retv = strcmp(classHints.res_name, panel->name);
XFree(classHints.res_name);
XFree(classHints.res_class);
if (!retv) return True;
}
/*
* If that failed, try looking at the WM name
*/
if (XGetWMName(XtDisplay(rootWin), win, &textProp)) {
int retv = strcmp(textProp.value, panel->name);
XFree(textProp.value);
if (!retv) return True;
}
/*
* And finally we try the icon name
*/
if (XGetWMIconName(XtDisplay(rootWin), win, &textProp)) {
int retv = strcmp(textProp.value, panel->name);
XFree(textProp.value);
if (!retv) return True;
}
return False;
}
/**************************************************************************
*
* Function: HandleInputCB
*
* Description: Called whenever the select in the event loop returns with
* input pending on the option panel pipe. Note that this routine is
* called both when data is pending and when the option panel dies.
* the option panel death case is identified by a read on the pipe
* returning zero.
*
* Parameters:
* clientData (I) - option panel structure
* fdp (I) - pointer to pipe fd
* inputID (I) - Xt ID for the input handler
*
* Return: none
*
**************************************************************************/
static void HandleInputCB(XtPointer clientData, int *fdp, XtInputId *inputID)
{
PuiOptionPanel *panel = (PuiOptionPanel*)clientData;
char inputBuf[PIPE_BUF];
int retv;
/*
* Make sure we have a valid root window widget
*/
rootWin = _PuiCreateRootWin(panel->parent, "_puiRootWin", NULL, 0);
/*
* Remove the event handler. Note that it may not be installed
* but it does not hurt to remove it directly.
*/
XtRemoveEventHandler(rootWin, SubstructureNotifyMask, False,
HandleMappingCB, (XtPointer)panel);
/*
* We read the pipe. If 0 is returned, the write end of the pipe
* has been closed and we assume the child has died. Otherwise we
* assume we have an options string.
*/
retv = read(*fdp, inputBuf, PIPE_BUF);
/*
* If an error occurred while reading we issue a callback
*/
if (retv < 0) {
HandleError(panel, errno);
return;
}
/*
* If we read 0 we call the death handler. The close the
* pty and call the death callback.
*/
if (retv == 0) {
HandleDeath(panel);
close(panel->ptyFd);
panel->ptyFd = -1;
CallDeathCallback(panel);
return;
}
/*
* If we actually read something, make sure it is
* NULL terminated and call the data handler
*/
inputBuf[(retv == PIPE_BUF)? retv - 1: retv] = '\0';
HandleData(panel, inputBuf);
}
/**************************************************************************
*
* Function: HandleDeath
*
* Description: Handles the death of an option panel program by removing
* any event handlers, input handlers, closing file descriptors and
* putting the optionpanel structure in an initialized state.
*
* Parameters:
* panel (I) - option panel structure
*
* Return: none
*
**************************************************************************/
static void HandleDeath(PuiOptionPanel *panel)
{
/*
* Make sure we have a valid root window widget
*/
rootWin = _PuiCreateRootWin(panel->parent, "_puiRootWin", NULL, 0);
/*
* The first thing to do is remove the input handler and
* close the pipe.
*/
if (panel->inputId) {
XtRemoveInput(panel->inputId);
XFlush(XtDisplay(rootWin));
}
if (panel->dataFd != -1)
close(panel->dataFd);
/*
* Remove the parent mapping event handler if we have been asked
* to follow the parent.
*/
if (panel->followParent)
XtRemoveEventHandler(panel->shell, StructureNotifyMask, False,
HandleParentCB, (XtPointer)panel);
/*
* Initialize structure variables
*/
panel->pid = 0;
panel->wid = None;
panel->wantWID = False;
panel->inputId = 0;
panel->dataFd = -1;
}
/**************************************************************************
*
* Function: CallDeathCallback
*
* Description: Calls the death callback function if any is registered.
*
* Parameters:
* panel (I) - option panel structure
*
* Return: none
*
**************************************************************************/
static void CallDeathCallback(PuiOptionPanel *panel)
{
PuiOptionPanelCallbackStruct cb;
if (!panel->deathCallback)
return;
InitCallback(cb);
cb.reason = PuiCR_OPTION_DEATH;
(panel->deathCallback)(panel, panel->deathClientData, &cb);
}
/**************************************************************************
*
* Function: HandleData
*
* Description: Sends the options string out on the data callback.
*
* Parameters:
* panel (I) - option panel structure
* optionStr (I) - NULL terminated option string
*
* Return: none
*
**************************************************************************/
static void HandleData(PuiOptionPanel *panel, char *optionStr)
{
PuiOptionPanelCallbackStruct cb;
char *cptr;
/*
* Terminate the string at the first newline
*/
if ((cptr = strchr(optionStr, '\n')) != NULL)
*cptr = '\0';
/*
* Call the data callback, if any
*/
if (!panel->dataCallback)
return;
InitCallback(cb);
cb.reason = PuiCR_OPTION_DATA;
cb.options = optionStr;
(panel->dataCallback)(panel, panel->dataClientData, &cb);
}
/**************************************************************************
*
* Function: RaisePanel
*
* Description: Forces the option panel window into the normal state and
* raises it to the top of the window stack.
*
* Parameters:
* panel (I) - option panel structure
*
* Return: none
*
**************************************************************************/
static void RaisePanel(PuiOptionPanel *panel)
{
static Atom WM_CHANGE_STATE = None;
XClientMessageEvent event;
int state;
/*
* Ensure we have a window to play with
*/
if (panel->wid == None)
return;
/*
* Get our winodw's state
*/
state = GetWMState(XtDisplay(panel->shell), panel->wid);
/*
* We send the window manager a request to deiconify our
* window if it is not in the normal state.
*/
if (state != NormalState) {
/*
* First get the atom if we have never done that
*/
if (WM_CHANGE_STATE == None)
WM_CHANGE_STATE = XInternAtom(XtDisplay(panel->shell),
"WM_CHANGE_STATE", False);
if (WM_CHANGE_STATE == None)
return;
/*
* Then send the deiconify event
*/
event.type = ClientMessage;
event.window = panel->wid;
event.message_type = WM_CHANGE_STATE;
event.format = 32;
event.data.l[0] = NormalState;
XSendEvent(XtDisplay(panel->shell),
RootWindowOfScreen(XtScreen(panel->shell)),
False, SubstructureRedirectMask | SubstructureNotifyMask,
(XEvent*)&event);
}
/*
* Make sure the window is at the top of the stack
*/
XRaiseWindow(XtDisplay(panel->shell), panel->wid);
}
/**************************************************************************
*
* Function: FindShell
*
* Description: Traverses back up through the widget hierarchy of the
* specified widget looking for a subclass of Shell.
*
* Parameters:
* w (I) - base widget from which to start upward parent traverse
*
* Return: Shell widget ancestor of the specified widget.
*
**************************************************************************/
static Widget FindShell(Widget w)
{
Widget shell = w;
while (shell) {
if (XtIsShell(shell))
break;
shell = XtParent(shell);
}
return ((shell)? shell: w);
}
/**************************************************************************
*
* Function: SetXSearchPath
*
* Description: Gets the current setting of XUSERFILESEARCHPATH and prepends
* on it the value of SPOOL_APP_DEFAULTS_DIR plus the printer name
* and then sets that as the new X search path.
*
* Parameters:
* printerName (I) - name of the printer
*
* Return: none
*
**************************************************************************/
static void SetXSearchPath(char *printerName)
{
char *origPath, *newPath;
char *varStr = "XUSERFILESEARCHPATH=";
char *spoolStr;
/*
* Form the spooling app-defaults path name
*/
spoolStr = (char*)XtMalloc(strlen(SPOOL_APP_DEFAULTS_DIR) +
strlen(printerName) + 10);
sprintf(spoolStr, "%s/%s/%%N", SPOOL_APP_DEFAULTS_DIR, printerName);
/*
* Get the original path, if any
*/
origPath = getenv("XUSERFILESEARCHPATH");
/*
* Allocate storage for the new path
*/
newPath = (char*)XtMalloc(strlen(varStr) +
((origPath)? strlen(origPath): 0) +
strlen(spoolStr) + 5);
/*
* For the new path
*/
strcpy(newPath, varStr);
strcat(newPath, spoolStr);
if (origPath) {
strcat(newPath, ":");
strcat(newPath, origPath);
}
/*
* Set new search path
*/
putenv(newPath);
/*
* Free storage for spool string. Do not free new env string
* since putenv references it.
*/
XtFree(spoolStr);
}